package registration;

import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

import msg.*;
import user.User;

import common.*;
import common.dal.Transaction;
import common.exceptions.*;
import common.extensions.*;
import common.interfaces.*;

/**
 * A class that Manages event registrations and consensuses using a single transaction
 * 
 * @author Gal Lalouche
 */
public class RegistrationManager extends AbstractManager {
	private static AtomicLong			counter;
	private final IEventRegistration	eventRegister;
	private final IConsensusManager		consensusManager;
	private final IVoteManager			voteManager;
	
	private RegistrationManager(Transaction t) {
		super(t);
		eventRegister = new DBEventRegister(t);
		consensusManager = new DBConsensusManager(t);
		voteManager = new DBVoteManager(t);
		
		try {
			if (counter == null) {
				counter = new AtomicLong(consensusManager.getLastId() + 1);
			}
		} catch (SQLException e) {
			counter = new AtomicLong();
		}
	}
	
	/**
	 * @param t
	 * @return The instance
	 */
	public static RegistrationManager getNewInstance(Transaction t) {
		return new RegistrationManager(t);
	}
	
	/**
	 * Clears all persisted data
	 * 
	 * @throws SQLException
	 */
	@Override
	public void clear() throws SQLException {
		logMethod();
		voteManager.clear();
		eventRegister.clear();
		consensusManager.clear();
		counter.set(0);
	}
	
	// private Transaction setNewTransaction() throws SQLException {
	// Transaction $ = new Transaction();
	// voteManager.setTransaction($);
	// eventRegister.setTransaction($);
	// consensusManager.setTransaction($);
	// return $;
	// }
	
	/**
	 * Gets all the events registered to by the user
	 * 
	 * @param user The user to check
	 * @param past If <code>true</code> past events will be included, if <code>false</code> or <code>null</code>, only
	 *            events that haven't taken place will be returned.
	 * @return All events registered by the user
	 * @throws InvalidUserException If the user is not registered to the system
	 * @throws SQLException
	 */
	public Collection<EventMessage> getAllRegistrationsByUser(User user, Boolean past)
			throws InvalidUserException, SQLException {
		Collection<EventMessage> registrations = eventRegister.getAllEventRegisteredByUser(user);
		final long now = new Date().getTime();
		if ((past == null) || (past == false)) {
			return Extender.where(registrations, new Predicate<EventMessage>() {
				@Override
				public Boolean calculate(EventMessage element) {
					return element.getTime() > now;
				}
			});
		} else {
			return registrations;
		}
	}
	
	/**
	 * Sets up the system; <b> will delete all data </b>
	 * 
	 * @throws SQLException
	 */
	@Override
	public void setup() throws SQLException {
		eventRegister.setup();
		consensusManager.setup();
		voteManager.setup();
		clear();
		logMethod();
	}
	
	/**
	 * @return {@code true} iff a table was dropped
	 * @throws SQLException
	 */
	@Override
	public boolean drop() throws SQLException {
		boolean result = voteManager.drop();
		result |= consensusManager.drop();
		result |= eventRegister.drop();
		logMethod();
		return result;
	}
	
	/**
	 * Registers a user to an event
	 * 
	 * @param user The user to register
	 * @param event The event to register to
	 * @return {@code true} iff the user was not already registered to the event
	 * @throws InvalidUserException If the user is not registered to the system
	 * @throws InvalidEventException If the event is not registered to the system
	 * @throws EventFullException If the event is already full
	 * @throws EventOverException If the event is already over
	 * @throws SQLException
	 */
	public boolean registerUserForEvent(User user, EventMessage event) throws InvalidUserException,
			EventFullException, EventOverException, SQLException, InvalidEventException {
		int amount = eventRegister.getTotalRegisteredTo(event);
		if (eventRegister.getAllEventRegisteredByUser(user).contains(event)) {
			return false;
		}
		// we know by now that the event has not been removed
		if (amount >= event.getCapacity()) {
			throw new EventFullException(event);
		} else {
			checkEventOver(event);
		}
		
		return eventRegister.register(user, event);
	}
	
	/**
	 * Registers a user to an event
	 * 
	 * @param user The user to register
	 * @param event The event to register to
	 * @return {@code true} iff the user was registered to the event and was unregistered successfully
	 * @throws EventOverException If the event is already over
	 * @throws SQLException
	 * @throws InvalidUserException
	 * @throws InvalidEventException
	 */
	public boolean unregisterUserForEvent(User user, EventMessage event) throws EventOverException,
			InvalidEventException, InvalidUserException, SQLException {
		checkEventOver(event);
		return eventRegister.unregister(user, event);
	}
	
	/**
	 * Adds a new consensus to the system
	 * 
	 * @param event The event the consensus needs to be reach on
	 * @param name The consensus' name
	 * @return The new consensus
	 * @throws InvalidEventException
	 * @throws EventOverException If the event is already over
	 * @throws SQLException
	 */
	public Consensus addConsensus(EventMessage event, String name) throws InvalidEventException,
			EventOverException, SQLException {
		checkEventOver(event);
		Consensus con = new Consensus(counter.getAndIncrement(), name, event);
		consensusManager.persist(con);
		return con;
	}
	
	private static void checkEventOver(EventMessage event) throws EventOverException {
		if (event == null) {
			throw new IllegalArgumentException("event is null");
		}
		if (event.isOver()) {
			throw new EventOverException(event);
		}
	}
	
	/**
	 * Removes a consensus from the system
	 * 
	 * @param conId The id of the consensus to remove
	 * @return <code>true</code> iff the consensus was removed
	 * @throws SQLException
	 */
	public boolean removeConsensus(long conId) throws SQLException {
		return consensusManager.delete(conId);
	}
	
	/**
	 * @param event The event
	 * @return All the consensus regarding the event
	 * @throws InvalidEventException
	 * @throws SQLException
	 */
	public Collection<Consensus> getAllEventConsensuses(EventMessage event) throws InvalidEventException,
			SQLException {
		return consensusManager.getAllEventConsensuses(event);
	}
	
	/**
	 * @param id The id to search
	 * @return The consensus with the id
	 * @throws InvalidKeyException If no consensus exists with the requested id
	 * @throws SQLException
	 */
	public Consensus getConsensus(long id) throws InvalidKeyException, SQLException {
		return consensusManager.get(id);
	}
	
	/**
	 * @param user The user
	 * @param event The event
	 * @return <code>true</code> iff the user is registered to the event
	 * @throws InvalidEventException
	 * @throws InvalidUserException
	 * @throws SQLException
	 */
	public boolean isUserRegisteredToEvent(User user, EventMessage event) throws InvalidEventException,
			InvalidUserException, SQLException {
		return eventRegister.isUserRegisteredToEvent(user, event);
	}
	
	/**
	 * @param event The event
	 * @return All users registered to the event
	 * @throws InvalidEventException
	 * @throws SQLException
	 */
	public Collection<User> getAllRegistrationsToEvent(EventMessage event) throws InvalidEventException,
			SQLException {
		return eventRegister.getAllUsersRegisteredToEvent(event);
	}
	
	/**
	 * Votes on a consensus
	 * 
	 * @param con The consensus to vote on
	 * @param user The user casting the vote
	 * @param vote The vote the user cast
	 * @throws InvalidConsensusException
	 * @throws InvalidUserException
	 * @throws EventOverException
	 * @throws IllegalArgumentException If the user is not registered to {@code con.getEvent()}
	 * @throws SQLException
	 */
	public void vote(Consensus con, User user, boolean vote) throws InvalidConsensusException,
			InvalidUserException, SQLException, EventOverException {
		if (con == null) {
			throw new IllegalArgumentException("con is null");
		}
		checkEventOver(con.getEvent());
		voteManager.vote(con, user, vote);
	}
	
	/**
	 * @param con The consensus The vote was cast on
	 * @param user The user voting
	 * @return The vote cast by the user, or {@code null} if the user has not yet voted
	 * @throws InvalidConsensusException
	 * @throws InvalidUserException
	 * @throws IllegalArgumentException If the user is not registered to {@code con.getEvent()}
	 * @throws SQLException
	 */
	public Boolean getVote(Consensus con, User user) throws InvalidConsensusException, InvalidUserException,
			SQLException {
		return voteManager.getVote(con, user);
	}
	
	/**
	 * Check if a consensus has been reached.<br>
	 * A consensus will be considered if all registered users voted the same.
	 * 
	 * @param con The consensus to check
	 * @return <code>true</code> if a consensus has been reached on true, <code>false</code> if on false,
	 *         <code>null</code> otherwise.
	 * @throws InvalidConsensusException
	 * @throws SQLException
	 */
	public Boolean getConsensusStatus(Consensus con) throws InvalidConsensusException, SQLException {
		return voteManager.getConsensusStatus(con);
	}
	
	/**
	 * @param con The consensus
	 * @return All votes cast on a consensus
	 * @throws InvalidConsensusException
	 * @throws SQLException
	 */
	public Collection<Pair<User, Boolean>> getAllVotes(Consensus con) throws InvalidConsensusException,
			SQLException {
		return voteManager.getAllVotes(con);
	}
	
	/**
	 * Remove all entities regarding the user's posted events (registrations, consensuses and votes).<br>
	 * Will not remove the events and messages posted by the user.
	 * 
	 * @param user The user
	 * @throws InvalidUserException
	 * @throws SQLException
	 */
	public void removeAllFromUser(User user) throws InvalidUserException, SQLException {
		Collection<EventMessage> events = MessageManager.getNewInstance(t).getallUserEvents(user, null, true);
		// remove all events created
		for (EventMessage event: events) {
			try {
				removeAllEventData(event);
			} catch (InvalidEventException e) {
				throw new AssertionError(e);
			}
		}
		// remove all user votes
		voteManager.removeAllVotes(user);
		// remove all registerations
		events = getAllRegistrationsByUser(user, false);
		for (EventMessage event: events) {
			try {
				eventRegister.unregister(user, event);
			} catch (InvalidEventException e) {
				throw new AssertionError(e);
			}
		}
	}
	
	/**
	 * Removes all event's registrations, consensuses and votes
	 * 
	 * @param event The event
	 * @throws InvalidEventException
	 * @throws SQLException
	 */
	public void removeAllEventData(EventMessage event) throws InvalidEventException, SQLException {
		eventRegister.removeAllEventRegisrations(event);
		for (Consensus con: getAllEventConsensuses(event)) {
			voteManager.removeAllVotes(con);
			consensusManager.delete(con.getId());
		}
	}
}